#include "dialogserveur.h"
#include "joueur.h"
#include "plateaurisq.h"
#include "etat.h"

#include "log.h"

DialogServeur::DialogServeur(QString boardname, int nbMaxJoueur, int port)
{
    // Création et disposition des widgets de la fenêtre
    etatServeur = new QLabel;
    boutonQuitter = new QPushButton(trUtf8("Quitter"));
    //connect(boutonQuitter, SIGNAL(clicked()), qApp, SLOT(quit()));
    connect(boutonQuitter, SIGNAL(clicked()), this, SLOT(fermeServeur()));

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(etatServeur);
    layout->addWidget(boutonQuitter);
    setLayout(layout);

    setWindowTitle(trUtf8("Serveur Chat"));

    // Gestion du serveur
    serveur = new QTcpServer(this);
    if (!serveur->listen(QHostAddress::Any, port)) // Démarrage du serveur sur toutes les IP disponibles et sur le port 50585
    {
        // Si le serveur n'a pas été démarré correctement
        etatServeur->setText(trUtf8("Le serveur n'a pas pu être démarré :<br />") + serveur->errorString());
    }
    else
    {
        QString val_ip ;
         foreach (QNetworkInterface bidule, QNetworkInterface::allInterfaces())
         {
             if (bidule.isValid() && (bidule.flags() == (0x1|0x2|0x4|0x20) ) && !(bidule.addressEntries().isEmpty()) )
             {
                 //bidule.IsUp && bidule.IsRunning && bidule.CanBroadcast && bidule.CanMulticast
                 qDebug() << bidule ;
                 qDebug() << bidule.humanReadableName();
                 qDebug() << bidule.flags() ;
                 qDebug() << bidule.addressEntries().at(1).ip() ;
                 qDebug() << "" ;
                 qDebug() << bidule.addressEntries().at(1).ip().protocol() ; // 0 =ipv4 1=ipv6
                 qDebug() << "\n\n\n" ;
                 foreach (QNetworkAddressEntry adr, bidule.addressEntries() )
                 {
                     if (adr.ip().protocol() == 0)
                     {
                        val_ip = adr.ip().toString();
                     }
                 }

             }
         }






        // Si le serveur a été démarré correctement
        etatServeur->setText(trUtf8("Le serveur est opérationnel : le port est <strong>") +
                             QString::number(serveur->serverPort()) +
                             trUtf8("</strong>.<br/>Votre adresse IP est :<strong>") +
                             val_ip +
                             trUtf8("</strong>.<br />Les joueurs peuvent maintenant se connecter."));

        connect(serveur, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));

        LOG1(OINFO, "Server OK sur le port %1", serveur->serverPort());
    }


    tailleMessage = 0;

    plateau = new PlateauRisQ();
    int r;
    if ( (r = plateau->chargerXml(plateau->getRessourcesPath() + boardname + ".xml")) < 0 ){
        LOG(OERREUR, "problème lors de l'ouverture du xml du plateau");
        return;
    }
    plateau->setPlateauNom(boardname);

    this->nbMaxJoueur = nbMaxJoueur;

    plateau->setnbMaxJoueurs(nbMaxJoueur);
    //LOG2(ODEBUG, "nombre de joueurs par get %1, %2",plateau->getnbMaxJoueurs(), nbMaxJoueur);
    // créer objectifs à mettre dans le plateau client à la fonction actInitialise
    // plateau->creerObjectifs();

    // initialise les missions pour les joueurs


    QList<int> liste_tmp;
    //LOG1(ODEBUG, "nombre de joueurs %1",nbMaxJoueur);
    plateau->addEnvInitialisation(QString::number(nbMaxJoueur)) ;
    int tmp ;
    do
    {
        tmp = qrand() % (8 + nbMaxJoueur) ;
    }while (tmp == 8 );

    int j;
    bool recommence = false;
    plateau->addEnvInitialisation(";");
    plateau->addEnvInitialisation(QString::number(tmp));
    liste_tmp.append(tmp);
//*
    for (int i = 1 ; i < nbMaxJoueur ; i++) // 14 missions pour 6 joueurs| 10 pour 2, 11/3 12/4 13/5 14/6
    {
        do
        {
            recommence = false;
            do
            {
                tmp = qrand() % (8 + nbMaxJoueur) ;
            }while (tmp == i + 8 ); // 9ème mission pour j1 impossible puisque détruire j1 = 9ème (1 à n sinon 0 à n-1 donnne 8ème=j1 == i=0)

            for (j=0 ; j<liste_tmp.count();j++)
            {
                if (tmp == liste_tmp.at(j))
                {
                    recommence = true ;
                }
            }
        }while (recommence);
        plateau->addEnvInitialisation(";");
        plateau->addEnvInitialisation(QString::number(tmp));
        liste_tmp.append(tmp);
    }
//*/




}

void DialogServeur::donneesRecues()
{
    // On détermine quel client envoie le message (recherche du QTcpSocket du client)
    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
    if (socket == 0) // Si par hasard on n'a pas trouvé le client à l'origine du signal, on arrête la méthode
        return;

    QDataStream in(socket);

    if (tailleMessage == 0)
    {
        if (socket->bytesAvailable() < (int)sizeof(quint16))
             return;

        in >> tailleMessage;
    }

    if (socket->bytesAvailable() < tailleMessage)
        return;

    /* Reconstruction du paquet
     * Format:
     * [size][action][donnee]
     * [uint16][uint16][string]
     **/
    quint16 uaction;
    in >> uaction;
    ActionNet action = static_cast<ActionNet>(uaction);

    QString donnee;
    in >> donnee;

    LOG3(ODEBUG, "donneesRecues: %1 (%2), donnee %3",
             ANet::getNom(action), action, donnee);

    tailleMessage = 0;

    dispatch(socket, action, donnee);

    /* Si il reste des donnée à traiter on rappel donneesRecues */
    if (socket->bytesAvailable() > 0 )
        donneesRecues();
}

void DialogServeur::dispatch(QTcpSocket *socket, ActionNet action, QString donnee)
{

    switch(action){
        case ACT_CONNECT:
            actConnect(socket, donnee);
            break;
        case ACT_JOUEURS_LIST:
            actJoueurList(socket);
            break;
        case ACT_MESSAGE:
            envoyerATous(action, donnee);
            break;
        case ACT_PAYS:
            actPays();
            break;

        case ACT_PION_ADD:
            envoyerATousSauf(socket, action, donnee);
            break;

        case ACT_PION_RM:
            envoyerATousSauf(socket, action, donnee);
            break;

        case ACT_CHANGE_STATUT:
            envoyerATous(action,donnee);
            break;

        case ACT_SET_JOUEUR:
            envoyerATous(action,donnee);
            break;


        default:
            LOG(ODEBUG, "commande inconnue");
            break;
    }
}

void DialogServeur::actConnect(QTcpSocket *socket, QString donnee)
{
    int idjoueur;

    idjoueur = plateau->joueurs().count();

    LOG2(ODEBUG, "connection du joueur %1 attribution de l'id' %2", donnee, idjoueur);

    plateau->addJoueur(new Joueur(idjoueur, donnee));

    /* Associe l'id du joueur au socket, bad way but easy */
    socket->setObjectName(QString::number(idjoueur));

    /* Envoie l'id du joueur */
    envoyerDonnees(socket, ACT_JOUEUR_ID, QString::number(idjoueur));

    /* Envoie la liste des joueurs connecté */
    actJoueurList(socket);

    /* Notify les autres joueurs */
    envoyerATousSauf(socket, ACT_JOUEUR_NEW_CON,
                     tr("%1;%2").arg(idjoueur).arg(donnee));

    // Dis au joueur d'initialiser son plateau (nbmaxjoueurs + créerObjectifs() )
    // pas très prudent d'envoyer toute la liste mais bon...
    envoyerDonnees(socket, ACT_INITIALISE, plateau->getEnvInitialisation() );

    /* Server complet, on assigne les pays au joueurs
     * (peut être déclenché par ACT_PAYS )
     *  @TODO Blocker les nouvelles connexions
     */
    if ( nbMaxJoueur <= plateau->joueurs().count()){
        actPays();
    }
}

void DialogServeur::actPays()
{
    plateau->assigneEtat();

    /* Construit la listes des états attribué
     * Format:
     * idetat:idpays;idetat:idpays; etc...
     */
    QString etatStr = "";
    foreach(Etat *e, plateau->etats()){
        etatStr += tr("%1:%2;").arg(e->getId()).arg(e->getJoueur()->getId());
    }
    envoyerATous(ACT_PAYS, etatStr);
}

void DialogServeur::actJoueurList(QTcpSocket *socket)
{
    /* Construit la listes des joueurs
     * Format:
     * idjoueur:nomjoueur;idjoueur:nomjoueur; etc...
     */
    QString joueursStr = "";
    foreach(Joueur *j, plateau->joueurs()){
        joueursStr += tr("%1:%2;").arg(j->getId()).arg(j->getNom());
    }
    envoyerDonnees(socket, ACT_JOUEURS_LIST, joueursStr);
}

void DialogServeur::envoyerDonnees(QTcpSocket *socket, ActionNet action, QString donnee)
{

    QByteArray paquet;
    QDataStream out(&paquet, QIODevice::WriteOnly);

    /* Construction du paquet
     * Format:
     * [size][action][donnee]
     * [uint16][uint16][string]
     **/
    out << (quint16) 0;
    out << (quint16)action;
    out << donnee;
    out.device()->seek(0);
    out << (quint16) (paquet.size() - sizeof(quint16));

    LOG3(ODEBUG, "envoyerDonnees: %1 (taille %2), donnee %3",
             ANet::getNom(action), (paquet.size() - sizeof(quint16)), donnee);

    /* On envoie le paquet */
    socket->write(paquet);

}


void DialogServeur::envoyerATous(ActionNet action, QString donnee)
{

    QByteArray paquet;
    QDataStream out(&paquet, QIODevice::WriteOnly);

    out << (quint16) 0;
    out << (quint16)action;
    out << donnee;
    out.device()->seek(0);
    out << (quint16) (paquet.size() - sizeof(quint16));

    LOG3(ODEBUG, "envoyerATous: %1 (taille %2), donnee %3",
             ANet::getNom(action), (paquet.size() - sizeof(quint16)), donnee);

    // Envoi du paquet préparé à tous les clients connectés au serveur
    for (int i = 0; i < clients.size(); i++)
    {
        clients[i]->write(paquet);
    }

}

void DialogServeur::envoyerATousSauf(QTcpSocket *socket, ActionNet action, QString donnee)
{

    QByteArray paquet;
    QDataStream out(&paquet, QIODevice::WriteOnly);

    out << (quint16) 0;
    out << (quint16)action;
    out << donnee;
    out.device()->seek(0);
    out << (quint16) (paquet.size() - sizeof(quint16));

    LOG3(ODEBUG, "envoyerATousSauf: %1 (taille %2), donnee %3",
             ANet::getNom(action), (paquet.size() - sizeof(quint16)), donnee);

    // Envoi du paquet préparé à tous les clients connectés au serveur
    for (int i = 0; i < clients.size(); i++)
    {
        if ( clients[i] == socket )
            continue;

        clients[i]->write(paquet);
    }

}

void DialogServeur::nouvelleConnexion()
{
    LOG(ODEBUG, "Un nouveau joueur vient de se connecter");

    QTcpSocket *nouveauClient = serveur->nextPendingConnection();

    if ( nbMaxJoueur <= plateau->joueurs().count()){
        LOG2(ODEBUG, "Serveur complet! (%1/%2)",
            plateau->joueurs().count(), nbMaxJoueur);
        /* Ferme le socket */
        nouveauClient->abort();
    }

    clients << nouveauClient;

    connect(nouveauClient, SIGNAL(readyRead()), this, SLOT(donneesRecues()));
    connect(nouveauClient, SIGNAL(disconnected()), this, SLOT(deconnexionClient()));
}

void DialogServeur::deconnexionClient()
{
    LOG(ODEBUG, "Un joueur vient de se déconnecter");

    /* On détermine quel client se déconnecte */
    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());

    /* Si par hasard on n'a pas trouvé le client
            à l'origine du signal, on arrête la méthode */
    if (socket == 0)
        return;

    /* Récupère l'id du joueur qui se déconnecte */
    int idjoueur = -1;
    if (socket->objectName().length() != 0){
        idjoueur = socket->objectName().toInt();
    }

    clients.removeOne(socket);

    socket->deleteLater();

    /* Notify les autres joueurs */
    if ( idjoueur > -1 ){
        //plateau->joueurs().removeJoueur(idjoueur);
        envoyerATous(ACT_JOUEUR_DECO,
                      tr("%1").arg(idjoueur));
    }


    /* Ferme le serveur si il n'y a plus de client,
     * l'appli ce ferme brutablement bizar */
    /*if (clients.count() == 0)
        fermeServeur();*/

}

void DialogServeur::fermeServeur()
{
    LOG(OINFO, "Fermeture du serveur ...");
    /*foreach(QTcpSocket *s, clients){
        s->close();
    }*/

    serveur->~QTcpServer();
    close();
}
